package cmp.compile;

import java.io.*;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Vector;

import jpave.annotator.CoreMain;

import cmp.main.Globals;
import util.Debug.ErrorReport;
import util.methods.BlastArgs;
import util.methods.HostsCfg;
import cmp.compile.panels.BlastPanel;
import cmp.compile.panels.CompilePanel;
import cmp.compile.panels.EditBlastPanel;
import assem.DBConn;

public class DoBlast {
	

	public static boolean runBlast(CompilePanel cmpPanel, String blastFile) {
		try {
			long startTime = CompileMain.getTime();
			String projName = cmpPanel.getProjectPanel().getSelectedProjectName();
			CompileMain.PrtDateMsg("Running Blast for " + projName);
					
			String fastaDir = cmpPanel.getCurrentProjectDirectory() + "/" + Globals.CompilePanel.BLASTDIR;
			String fastaFile = fastaDir + "/" + Globals.CompilePanel.COMBINEDFASTA;  // combined.fasta			
			File testDir = new File(fastaDir);
			if(!testDir.exists()) { // should have been made during Create database
				if (!CompileMain.generateFastaFromDB(cmpPanel)) return false;
			}
			int n = cmpPanel.getBlastPanel().getCPUs();
			if (n==0) return false;
			
			fastaFile = (new File(fastaFile)).getAbsolutePath();
			blastFile = (new File(blastFile)).getAbsolutePath();	
			
			if(!cmpPanel.getProjectPanel().doesDBExist()) {
				CompileMain.PrtWarn("Execute \'Create Database\' first");
				return false;
			}
			
			String formatCmd = BlastArgs.getFormatp(fastaFile);
			CompileMain.PrtSpMsg(1, "Executing: " + formatCmd);
			Process pFormatDB = Runtime.getRuntime().exec(formatCmd);
			pFormatDB.waitFor();
			String threads = (BlastArgs.isBlastPlus()) ? " -num_threads " : " -a";
			String blastCmd = BlastArgs.getBlastpExec() + threads + n +  " " + 
					cmpPanel.getBlastPanel().getBlastParams() + " " +
					BlastArgs.getBlastArgs(fastaFile, fastaFile, blastFile);  ;
 			
			StreamTokenizer stringTokens = new StreamTokenizer(new StringReader(blastCmd));
			stringTokens.resetSyntax();
			stringTokens.wordChars(0, 255);
			stringTokens.whitespaceChars(32, 32);
			stringTokens.quoteChar('\'');
			
			Vector<String> theTokens = new Vector<String> ();
			while(stringTokens.nextToken() != StreamTokenizer.TT_EOF) {
				theTokens.add(stringTokens.sval);
			}
			CompileMain.PrtSpMsg(1, "Executing: " + blastCmd);
			String line = "";
			Process pBlastall = Runtime.getRuntime().exec(theTokens.toArray(new String[0]));
			BufferedReader input = new BufferedReader(new InputStreamReader(pBlastall.getErrorStream()));
			while ((line = input.readLine()) != null) {
				System.out.println(line);
			}
			input.close();
			int result = pBlastall.waitFor();
			if(result != 0)
				System.err.println("Error: blast call failed");
			else {
				cmpPanel.getBlastPanel().setBlastFile(blastFile);
				cmpPanel.getBlastPanel().setUseBlastPanel(true);
			}

			if (cmpPanel.getBlastPanel().filterBlast() && !(new File(cmpPanel.getBlastPanel().getFilterBlastName())).exists())
			{
				cleanBlast(new File(blastFile),cmpPanel);
			}
			CompileMain.PrtDateMsgTime("Complete BLAST processing", startTime);
			return true;
		}
		catch(Exception e) {
			ErrorReport.prtReport(e, "Error running blast");
			return false;
		}
	}
	
	public static void cleanBlast(File blastFile,CompilePanel cmpPanel) {
		try {
			CompileMain.PrtDateMsg("\nFilter blast file");	
			long startTime = CompileMain.getTime();
			HashMap<String,Integer> lengths = new HashMap<String,Integer>();
			HashSet<String> redundant = new HashSet<String>();
			DBConn cmpDBC = CompileMain.getDBConnection(cmpPanel);
			ResultSet rs = cmpDBC.executeQuery("select UTstr,length(aaseq) from unitrans where aaseq != ''");
			while (rs.next())
			{
				lengths.put(rs.getString(1), rs.getInt(2));
			}
			CompileMain.PrtSpMsg(1, "Read " + lengths.size() + " AA sequence");
			int minRedPctID = Integer.parseInt(cmpPanel.getBlastPanel().getFilterID());
			int maxRedExtra = Integer.parseInt(cmpPanel.getBlastPanel().getFilterExtra());

			BufferedReader br = new BufferedReader(new InputStreamReader(new DataInputStream(new FileInputStream(blastFile))));
			// Read once to get redundant
			CompileMain.PrtSpMsg(1, "First pass - determine redundant");
			String curLine = "";
			int cntRead=0;
			while((curLine = br.readLine()) != null) {
				cntRead++;
				if (curLine.length() == 0 || curLine.charAt(0) == '#') continue; 

				String tokens[] = curLine.split("\t");
				String name1 = tokens[0].trim();
				String name2 = tokens[1].trim();
				
				if(name1.equals(name2)) continue;
				
				if (name1.contains("|"))
				{
					name1 = name1.split("\\|")[1];
				}
				if (name2.contains("|"))
				{
					name2 = name2.split("\\|")[1];
				}				
				int len1, len2;
				if (!lengths.containsKey(name1))
				{
					CompileMain.PrtError("AA sequence not found for " + name1);
				}
				if (!lengths.containsKey(name2))
				{
					CompileMain.PrtError("AA sequence not found for " + name2);
				}				
				len1 = lengths.get(name1);
				len2 = lengths.get(name2);
				
				Float id = Float.valueOf(tokens[2].trim());
				
				int s1 = Integer.parseInt(tokens[6]);
				int e1 = Integer.parseInt(tokens[7]);
				int s2 = Integer.parseInt(tokens[8]);
				int e2 = Integer.parseInt(tokens[9]);
				
				int extra1 = len1-(e1-s1+1); 
				int extra2 = len2-(e2-s2+1); 
				
				boolean red1 = false;
				boolean red2 = false;
				if (id >= minRedPctID)
				{
					// Note, explicit length checks because gaps can cause a longer sequence to appear
					// contained in a shorter.
					if (extra1 <= maxRedExtra && len2 >= len1)
					{
						red1=true;
					} 
					if (extra2 <= maxRedExtra && len1 >= len2) 
					{
						red2=true;
					}
				}
				if (red1 && red2)
				{
					// They must have the same length. Prefer the alphabetically first, to avoid loops.
					// Note, we don't try to compare the extras, which could result in loops, because
					// gaps/mismatches can create unpredictable results.

					if (name1.compareTo(name2) < 0)
					{
						red1 = false;
					}
					else 
					{
						red2 = false;
					}
					
				}
				if (red1 && !redundant.contains(name1))
				{
					//System.out.println("RED:" + name1 + ":" + len1 + ":" + name2 + ":" + len2 + ":" + extra1 + ":" + extra2 + "\n" + curLine);
					redundant.add(name1);
				}
				if (red2 && !redundant.contains(name2))
				{
					//System.out.println("RED:" + name2 + ":" + len2 + ":" + name1 + ":" + len1 + ":" + extra1 + ":" + extra2 + "\n" + curLine);
					redundant.add(name2);
				}
				if (cntRead % 10000 == 0) {
					System.err.print("   Read " + cntRead + " blast lines\r");
				}
			}
			br.close();
			br = new BufferedReader(new InputStreamReader(new DataInputStream(new FileInputStream(blastFile))));
			File outFile = new File(cmpPanel.getBlastPanel().getFilteredBlastFileName());
			if (outFile.exists()) outFile.delete();
			PrintWriter pw = new PrintWriter(new FileWriter(outFile));
			int cnt=0, cntFil=0;
			cntRead=0;
			
			CompileMain.PrtSpMsg(1, "Second pass - create filtered file " + outFile.getName());
			while((curLine = br.readLine()) != null) {
				cntRead++;
				if (curLine.length() == 0 || curLine.charAt(0) == '#') continue; 

				String tokens[] = curLine.split("\t");
				String name1 = tokens[0].trim();
				String name2 = tokens[1].trim();
				
				if(name1.equals(name2)) continue;
				
				if (name1.contains("|"))
				{
					name1 = name1.split("\\|")[1];
				}
				if (name2.contains("|"))
				{
					name2 = name2.split("\\|")[1];
				}	
				
				if (!redundant.contains(name1) && !redundant.contains(name2))
				{
					pw.write(curLine + "\n");
					pw.flush();
					cnt++;
				}
				else cntFil++;
				if (cntRead % 10000 == 0) {
					System.err.print("   Read " + cntRead + " blast lines\r");
				}
			}
			CompileMain.PrtSpMsgTime(0, "Wrote " + cnt + " filtered BLAST results; removed " + cntFil + " results", startTime);
			cmpPanel.getBlastPanel().setBlastSummary();
			pw.close();
			br.close();
		}
		catch(Exception e) {
			ErrorReport.prtReport(e, "Error cleaning blast file");
		}
	}
}
